// Copyright Epic Games, Inc. All Rights Reserved.

#include "ToonOutLineRendering.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "PrimitiveSceneProxy.h"
#include "MeshPassProcessor.inl"
#include "ScenePrivate.h"
#include "DeferredShadingRenderer.h"
#include "RenderCore.h"

class FOutLinePassVS : public FMeshMaterialShader
{
public:
	DECLARE_SHADER_TYPE(FOutLinePassVS, MeshMaterial);

	static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters,
		FShaderCompilerEnvironment& OutEnvironment){}

	static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
	{

		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) &&
			Parameters.MaterialParameters.bUseOutLine;
	}

	FOutLinePassVS(){};
	FOutLinePassVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
		: FMeshMaterialShader(Initializer)
	{
		OutLineScale.Bind(Initializer.ParameterMap,TEXT("OutLineScale"));
	}

    void GetShaderBindings(
	const FScene* Scene,
	ERHIFeatureLevel::Type FeatureLevel,
	const FPrimitiveSceneProxy* PrimitiveSceneProxy,
	const FMaterialRenderProxy& MaterialRenderProxy,
	const FMaterial& Material,
	const FMeshMaterialShaderElementData& ShaderElementData,
	FMeshDrawSingleShaderBindings& ShaderBindings) const
	{
		FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, ShaderElementData, ShaderBindings);
		ShaderBindings.Add(OutLineScale, Material.GetOutLineSize());
	}
private:
	LAYOUT_FIELD(FShaderParameter, OutLineScale)
};

class FOutLinePassPS : public FMeshMaterialShader
{
public:
	DECLARE_SHADER_TYPE(FOutLinePassPS, MeshMaterial);

	static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
	{
		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) &&
			Parameters.MaterialParameters.bUseOutLine;
    
	}

	static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
	{

	}

	FOutLinePassPS(){};
	FOutLinePassPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
		: FMeshMaterialShader(Initializer)
	{
		OutLineColor.Bind(Initializer.ParameterMap,TEXT("OutLineColor"));
	}

    void GetShaderBindings(
	const FScene* Scene,
	ERHIFeatureLevel::Type FeatureLevel,
	const FPrimitiveSceneProxy* PrimitiveSceneProxy,
	const FMaterialRenderProxy& MaterialRenderProxy,
	const FMaterial& Material,
	const FMeshMaterialShaderElementData& ShaderElementData,
	FMeshDrawSingleShaderBindings& ShaderBindings) const
	{
		FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, ShaderElementData, ShaderBindings);
		FLinearColor MatOutLinrColor = Material.GetOutLineColor();
		FVector4f OutLineColor4f = FVector4f(MatOutLinrColor.R,MatOutLinrColor.G,MatOutLinrColor.B,MatOutLinrColor.A);
		ShaderBindings.Add(OutLineColor, OutLineColor4f);
	}
private:
	LAYOUT_FIELD(FShaderParameter, OutLineColor)
};

IMPLEMENT_SHADER_TYPE(, FOutLinePassVS, TEXT("/Engine/Private/Toon/ToonOutLinePassShader.usf"), TEXT("MainVS"), SF_Vertex);
IMPLEMENT_SHADER_TYPE(, FOutLinePassPS, TEXT("/Engine/Private/Toon/ToonOutLinePassShader.usf"), TEXT("MainPS"), SF_Pixel);
IMPLEMENT_SHADERPIPELINE_TYPE_VSPS(OutLinePassPipeline, FOutLinePassVS, FOutLinePassPS, true);


bool GetOutLinePassShaders(
	const FMaterial& Material,
	const FVertexFactoryType* VertexFactoryType,
	ERHIFeatureLevel::Type FeatureLevel,
	TShaderRef<FOutLinePassVS>& VertexShader,
	TShaderRef<FOutLinePassPS>& PixelShader,
	FShaderPipelineRef& ShaderPipeline)
{
	FMaterialShaderTypes ShaderTypes;
	// ShaderTypes.PipelineType = &OutLinePassPipeline;
	ShaderTypes.AddShaderType<FOutLinePassVS>();
	ShaderTypes.AddShaderType<FOutLinePassPS>();

	FMaterialShaders Shaders;
	if (!Material.TryGetShaders(ShaderTypes, VertexFactoryType, Shaders))
	{
		return false;
	}

	Shaders.TryGetPipeline(ShaderPipeline);
	Shaders.TryGetVertexShader(VertexShader);
	Shaders.TryGetPixelShader(PixelShader);

	return true;
}

void FOutLinePassMeshProcessor::AddMeshBatch(
	const FMeshBatch& RESTRICT MeshBatch, 
	uint64 BatchElementMask, 
	const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, 
	int32 StaticMeshId /* = -1 */ 
	)
{
	if (MeshBatch.bUseForMaterial)
	{
		const FMaterialRenderProxy* MaterialRenderProxy = MeshBatch.MaterialRenderProxy;
		while (MaterialRenderProxy)
		{
			const FMaterialRenderProxy* FallBackMaterialRenderProxyPtr = nullptr;
			const FMaterial* Material = MaterialRenderProxy->GetMaterialNoFallback(FeatureLevel);
			if (Material && Material->GetRenderingThreadShaderMap())
			{
				const FMaterialShadingModelField ShadingModels = Material->GetShadingModels();
				// 只有材质使用了Toon相关的shading model才会被绘制
				if (ShadingModels.HasShadingModel(MSM_ToonDef) || ShadingModels.HasShadingModel(MSM_ToonFace) || ShadingModels.HasShadingModel(MSM_ToonMore))
				{
					// const EBlendMode BlendMode = Material->GetBlendMode();
					// if (BlendMode == BLEND_Opaque)
					// {
						if (TryAddMeshBatch(MeshBatch, BatchElementMask, PrimitiveSceneProxy, StaticMeshId, *MaterialRenderProxy, *Material))
						{
							break;
						}
					// }
				}
			}

			MaterialRenderProxy = MaterialRenderProxy->GetFallback(FeatureLevel);
		}
	}
}

bool FOutLinePassMeshProcessor::TryAddMeshBatch(
	const FMeshBatch& RESTRICT MeshBatch,
	uint64 BatchElementMask,
	const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
	int32 StaticMeshId,
	const FMaterialRenderProxy& MaterialRenderProxy,
	const FMaterial& Material)
{
		const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch);
		const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(Material, OverrideSettings);
		const ERasterizerCullMode MeshCullMode = CM_CCW;

		return Process(MeshBatch, BatchElementMask, StaticMeshId, PrimitiveSceneProxy, MaterialRenderProxy, Material, MeshFillMode, MeshCullMode);
}

bool FOutLinePassMeshProcessor::Process(
	const FMeshBatch& MeshBatch, 
	uint64 BatchElementMask, 
	int32 StaticMeshId, 
	const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
	const FMaterialRenderProxy& RESTRICT MaterialRenderProxy, 
	const FMaterial& RESTRICT MaterialResource,
	ERasterizerFillMode MeshFillMode, 
	ERasterizerCullMode MeshCullMode 
	)
{
	if(Scene->GetShadingPath()==EShadingPath::Deferred&&MaterialResource.UseOutLine())
	{
		const FVertexFactory* VertexFactory = MeshBatch.VertexFactory;

		TMeshProcessorShaders<FOutLinePassVS,FOutLinePassPS> OutLinePassShaders;

		FShaderPipelineRef ShaderPipeline;

		if (!GetOutLinePassShaders(
			MaterialResource,
			VertexFactory->GetType(),
			FeatureLevel,
			OutLinePassShaders.VertexShader,
			OutLinePassShaders.PixelShader,
			ShaderPipeline))
		{
			return false;
		}

		FMeshMaterialShaderElementData ShaderElementData;
		ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, StaticMeshId, false);

		const FMeshDrawCommandSortKey SortKey = CalculateMeshStaticSortKey(OutLinePassShaders.VertexShader, OutLinePassShaders.PixelShader);

		PassDrawRenderState.SetDepthStencilState(
		TStaticDepthStencilState<
		true, CF_GreaterEqual,// Enable DepthTest, It reverse about OpenGL(which is less)
		false, CF_Never, SO_Keep, SO_Keep, SO_Keep,
		false, CF_Never, SO_Keep, SO_Keep, SO_Keep,// enable stencil test when cull back
		0x00,// disable stencil read
		0x00>// disable stencil write
		::GetRHI());

		BuildMeshDrawCommands(
			MeshBatch,
			BatchElementMask,
			PrimitiveSceneProxy,
			MaterialRenderProxy,
			MaterialResource,
			PassDrawRenderState,
			OutLinePassShaders,
			MeshFillMode,
			MeshCullMode,
			SortKey,
			EMeshPassFeatures::Default,
			ShaderElementData
			);
	}

	return true;
}


BEGIN_SHADER_PARAMETER_STRUCT(FOutLinePassParameters, )
	SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View)
	SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures)
	SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams, InstanceCullingDrawParams)
	RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()


DECLARE_GPU_DRAWCALL_STAT_NAMED(OutLinePass, TEXT("Out Line Pass"));
// DECLARE_CYCLE_STAT(TEXT("OutLinePass"), STAT_CLP_OutLinePass, STATGROUP_ParallelCommandListMarkers);

void FDeferredShadingSceneRenderer::RenderOutLinePass(
	FRDGBuilder& GraphBuilder,
	TArrayView<FViewInfo> InViews,
	FSceneTextures& SceneTextures
)
{
	// RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, RenderOutLinePass);
	// SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_RenderOutLinePass, FColor::Emerald);
	//SCOPE_CYCLE_COUNTER(STAT_OutLinePassPassDrawTime);
	RDG_GPU_STAT_SCOPE(GraphBuilder, OutLinePass);
	RDG_EVENT_SCOPE(GraphBuilder, "OutLine Pass");

	for (int32 ViewIndex = 0; ViewIndex < InViews.Num(); ++ViewIndex)
	{
		FViewInfo& View = InViews[ViewIndex];
		RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
		RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, InViews.Num() > 1, "View%d", ViewIndex);


		const bool bShouldRenderView = View.ShouldRenderView();
		if (bShouldRenderView)
		{
			View.BeginRenderView();

			auto* PassParameters = GraphBuilder.AllocParameters<FOutLinePassParameters>();
			PassParameters->View = View.GetShaderParameters();
			PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(SceneTextures.Depth.Target, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilWrite);
			PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneTextures.Color.Target, ERenderTargetLoadAction::ELoad);

			View.ParallelMeshDrawCommandPasses[EMeshPass::OutLinePass].BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);

			GraphBuilder.AddPass(
				RDG_EVENT_NAME("OutLinePass"),
				PassParameters,
				ERDGPassFlags::Raster,
				[this, &View, PassParameters](FRHICommandList& RHICmdList)
				{
					SetStereoViewport(RHICmdList, View, 1.0f);
					View.ParallelMeshDrawCommandPasses[EMeshPass::OutLinePass].DispatchDraw(nullptr, RHICmdList, &PassParameters->InstanceCullingDrawParams);
				});
		}
	}
}

void SetupOutLinePassState(FMeshPassProcessorRenderState& DrawRenderState)
{
	DrawRenderState.SetBlendState(TStaticBlendState<>::GetRHI());
	DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<true, CF_DepthNearOrEqual>::GetRHI());
}

FOutLinePassMeshProcessor::FOutLinePassMeshProcessor(
	EMeshPass::Type InMeshPassType,
	const FScene* Scene, 
	ERHIFeatureLevel::Type InFeatureLevel,
	const FSceneView* InViewIfDynamicMeshCommand,
	const FMeshPassProcessorRenderState& InPassDrawRenderState, 
	FMeshPassDrawListContext* InDrawListContext
	)
	: FMeshPassProcessor(InMeshPassType, Scene, InFeatureLevel, InViewIfDynamicMeshCommand, InDrawListContext)
	, PassDrawRenderState(InPassDrawRenderState)
{
	if (PassDrawRenderState.GetDepthStencilState() == nullptr)
	{
		PassDrawRenderState.SetDepthStencilState(TStaticDepthStencilState<true, CF_DepthNearOrEqual>().GetRHI());
	}
	if (PassDrawRenderState.GetBlendState() == nullptr)
	{
		PassDrawRenderState.SetBlendState(TStaticBlendState<>().GetRHI());
	}
}

FMeshPassProcessor* CreateOutLinePassProcessor(ERHIFeatureLevel::Type InFeatureLevel, const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext)
{
	// const ERHIFeatureLevel::Type FeatureLevel = InViewIfDynamicMeshCommand ? InViewIfDynamicMeshCommand->GetFeatureLevel() : InFeatureLevel;

	 FMeshPassProcessorRenderState PassDrawRenderState;
	 SetupOutLinePassState(PassDrawRenderState);

	return new FOutLinePassMeshProcessor(EMeshPass::OutLinePass, Scene, InFeatureLevel, InViewIfDynamicMeshCommand, PassDrawRenderState, InDrawListContext);
}

REGISTER_MESHPASSPROCESSOR_AND_PSOCOLLECTOR(OutLinePass, CreateOutLinePassProcessor, EShadingPath::Deferred, EMeshPass::OutLinePass, EMeshPassFlags::CachedMeshCommands | EMeshPassFlags::MainView);